/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package edu.ccut.titan.component.loader;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.osgi.framework.Bundle;

import edu.ccut.titan.component.IBaseComponentInfo;
import edu.ccut.titan.component.IComponentInfo;
import edu.ccut.titan.component.INodeInfo;
import edu.ccut.titan.component.IParameterInfo;
import edu.ccut.titan.component.TitanComponentException;
import edu.ccut.titan.component.impl.TitanActionComponentInfo;
import edu.ccut.titan.component.impl.TitanBaseComponentInfo;
import edu.ccut.titan.component.impl.TitanLogicComponentInfo;
import edu.ccut.titan.component.impl.TitanParameterInfo;
import edu.ccut.titan.component.impl.node.BaseComponentNodeInfo;
import edu.ccut.titan.component.impl.node.EndNodeInfo;
import edu.ccut.titan.component.impl.node.LogicComponentNodeInfo;
import edu.ccut.titan.component.impl.node.StartNodeInfo;
import edu.ccut.titan.component.impl.node.ViewNodeInfo;
import edu.ccut.titan.component.xml.Dom4jUtils;

/**
 * Load Component information from a xml file. include : tita-component.xml,
 * *.logci.xml, *.action.xml
 * 
 * @author LeslieGu
 */
public abstract class ComponentLoader {

	private static Logger logger = Logger.getLogger(ComponentLoader.class
			.getPackage().getName());

	private static final String KEY_COMPONET = "componet";

	private static final String KEY_MODULE_KEY = "key";

	private static final String KEY_MODULE_NAME = "name";

	private static final String KEY_MODULE_DESCRIPTION = "description";

	private static final String KEY_COMPONENT_CLASS = "class";

	private static final String KEY_INFORMATION = "information";

	private static final String KEY_NODETYPE_START = "start";

	private static final String KEY_NODETYPE_END = "end";

	private static final String KEY_NODETYPE_COMPONENT = "component";

	private static final String KEY_NODETYPE_VIEW = "view";

	private static final String KEY_NODETYPE_LOGIC = "logic";

	private static final String KEY_NODE_ID = "id";

	private static final String KEY_NODE_NEXT = "next";

	private static final String KEY_NODE_NAME = "name";

	private static final String KEY_NODE_DESCRIPTION = "description";

	private static final String KEY_NODE_KEY = "key";

	private static final String KEY_NODE_LOCATION = "location";

	private static final String KEY_NODE_TYPE = "type";

	private static final String KEY_PARAMETER = "parameter";

	private static final String KEY_PARAMETER_LIST = "parameters";

	private static final String KEY_PARAMETER_ID = "id";

	private static final String KEY_PARAMETER_NAME = "name";

	private static final String KEY_PARAMETER_CLASS_TYPE = "classType";

	private static final String KEY_PARAMETER_PASSING_TYPE = "passingType";

	private static final String KEY_PARAMETER_VALUE_KEY = "value-key";

	private static final String KEY_PARAMETER_DECRRIPTION = "description";

	private static final String KEY_PARAMETER_SCOPE = "scope";

	/**
	 * 读取组件信息列表
	 * 
	 * @param bundle
	 * @param filePath
	 * @return
	 * @throws TitanComponentException
	 */
	public static List<IComponentInfo> loadBaseComponent(Bundle bundle,
			String filePath) throws TitanComponentException {
		InputStream in = null;
		try {
			URL url = bundle.getEntry(filePath);
			logger.debug("start load base components from \"" + url + "\"");
			in = url.openStream();
			return loadBaseComponent(bundle, in);
		} catch (Exception e) {
			throw new TitanComponentException("load file failed in bundle[key="
					+ bundle.getSymbolicName() + "] with file name ["
					+ filePath + "]", e);
		} finally {
			try {
				if (in != null) {
					in.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			logger.debug("end load base components");
			logger.debug("");
		}
	}

	/**
	 * 通过InputStream读取组件信息列表
	 * 
	 * @param bundleSymbolicName
	 *            组件所在Bundle的唯一标识
	 * @param in
	 * @return
	 * @throws DocumentException
	 * @throws TitanComponentException
	 */
	@SuppressWarnings("unchecked")
	public static List<IComponentInfo> loadBaseComponent(Bundle bundle,
			InputStream in) throws TitanComponentException {

		Document document = Dom4jUtils.getDocment(in);
		Element root = document.getRootElement();

		List<Element> components = root.elements(KEY_COMPONET);
		List<IComponentInfo> componentInfos = new ArrayList<IComponentInfo>(
				components.size());

		for (Element componentElement : components) {
			componentInfos
					.add(createBaseComponentInfo(bundle, componentElement));
		}

		return componentInfos;
	}

	/**
	 * 读取业务组件信息
	 * 
	 * @param bundle
	 * @param filePath
	 * @return
	 * @throws TitanComponentException
	 */
	public static IComponentInfo loadLogicComponentInfo(Bundle bundle,
			String filePath) throws TitanComponentException {
		InputStream in = null;
		try {
			URL url = bundle.getEntry(filePath);
			logger.debug("start logic component from \"" + url + "\"");
			in = url.openStream();
			return loadLogicComponentInfo(bundle, in);
		} catch (Exception e) {
			throw new TitanComponentException("can't find file in "
					+ bundle.getSymbolicName() + " with name " + filePath, e);
		} finally {
			try {
				if (in != null) {
					in.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			logger.debug("end load logic component");
			logger.debug("");
		}
	}

	/**
	 * 读取业务组件信息
	 * 
	 * @param bundle
	 * @param in
	 * @return
	 * @throws TitanComponentException
	 */
	public static IComponentInfo loadLogicComponentInfo(Bundle bundle,
			InputStream in) throws TitanComponentException {
		Document document = Dom4jUtils.getDocment(in);
		Element root = document.getRootElement();

		TitanLogicComponentInfo titanLogicComponentInfo = createLogicComponentInfo(
				bundle, root);
		
		titanLogicComponentInfo.setStartNodeInfo(getStartNodeInfo(bundle, root));
		titanLogicComponentInfo.addNodeInfos(getBaseComponentNodeInfo(bundle,
				root));
		titanLogicComponentInfo.addNodeInfos(getLogicComponentNodeInfo(bundle,
				root));
		titanLogicComponentInfo.addNodeInfos(getEndNodeInfo(bundle, root));

		return titanLogicComponentInfo;
	}

	/**
	 * 读取展现组件信息
	 * 
	 * @param bundle
	 * @param filePath
	 * @return
	 * @throws TitanComponentException
	 */
	public static IComponentInfo loadActionComponentInfo(Bundle bundle,
			String filePath) throws TitanComponentException {
		InputStream in = null;
		try {
			URL url = bundle.getEntry(filePath);
			logger.debug("start action component from \"" + url + "\"");
			in = url.openStream();
			return loadActionComponentInfo(bundle, in);
		} catch (Exception e) {
			throw new TitanComponentException("load file failed in bundls[key="
					+ bundle.getSymbolicName() + "] with file name ["
					+ filePath + "]", e);
		} finally {
			try {
				if (in != null) {
					in.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}

			logger.debug("end load action component");
			logger.debug("");
		}
	}

	/**
	 * load action component information
	 * 
	 * @param bundle
	 * @param in
	 * @return
	 * @throws TitanComponentException
	 */
	public static IComponentInfo loadActionComponentInfo(Bundle bundle,
			InputStream in) throws TitanComponentException {
		Document document = Dom4jUtils.getDocment(in);
		Element root = document.getRootElement();

		TitanActionComponentInfo titanActionComponentInfo = createActionComponentInfo(
				bundle, root);
		
		titanActionComponentInfo.setStartNodeInfo(getStartNodeInfo(bundle, root));
		titanActionComponentInfo.addNodeInfos(getLogicComponentNodeInfo(bundle,
				root));
		titanActionComponentInfo.addNodeInfos(getViewNodeInfo(root));

		return titanActionComponentInfo;
	}

	@SuppressWarnings("unchecked")
	private static List<INodeInfo> getViewNodeInfo(Element root)
			throws TitanComponentException {

		List<Element> viewNodeElements = root.elements(KEY_NODETYPE_VIEW);
		List<INodeInfo> viewNodeInfos = new ArrayList<INodeInfo>(
				viewNodeElements.size());

		for (Element baseComponentElement : viewNodeElements) {

			String id = baseComponentElement.attributeValue(KEY_NODE_ID);
			String name = baseComponentElement.elementText(KEY_NODE_NAME);
			String description = baseComponentElement
					.elementText(KEY_NODE_DESCRIPTION);
			String location = baseComponentElement
					.elementText(KEY_NODE_LOCATION);
			String type = baseComponentElement.elementText(KEY_NODE_TYPE);

			logger.debug("load view component id = \"" + id + "\"");
			logger.debug("load view component name = \"" + name + "\"");
			logger.debug("load view component description = \"" + description
					+ "\"");
			logger.debug("load view component location = \"" + location + "\"");
			logger.debug("load view component type = \"" + type + "\"");

			ViewNodeInfo baseComponentNodeInfo = new ViewNodeInfo(id, name,
					description, location, type);

			viewNodeInfos.add(baseComponentNodeInfo);
		}

		return viewNodeInfos;
	}

	private static TitanActionComponentInfo createActionComponentInfo(
			Bundle bundle, Element root) throws TitanComponentException {
		Element information = root.element(KEY_INFORMATION);

		String key = information.elementText(KEY_MODULE_KEY);
		String name = information.elementText(KEY_MODULE_NAME);
		String description = information.elementText(KEY_MODULE_DESCRIPTION);

		logger.debug("load action component symbolicName = \""
				+ bundle.getSymbolicName() + "\"");
		logger.debug("load action component key = \"" + key + "\"");
		logger.debug("load action component name = \"" + name + "\"");
		logger.debug("load action component description = \"" + description
				+ "\"");

		TitanActionComponentInfo titanActionComponentInfo = new TitanActionComponentInfo(
				bundle.getSymbolicName(), key, name, description);

		return titanActionComponentInfo;
	}

	private static TitanLogicComponentInfo createLogicComponentInfo(
			Bundle bundle, Element root) throws TitanComponentException {

		Element information = root.element(KEY_INFORMATION);
		String key = information.elementText(KEY_MODULE_KEY);
		String name = information.elementText(KEY_MODULE_NAME);
		String description = information.elementText(KEY_MODULE_DESCRIPTION);

		logger.debug("load logic component symbolicName = \""
				+ bundle.getSymbolicName() + "\"");
		logger.debug("load logic component key = \"" + key + "\"");
		logger.debug("load logic component name = \"" + name + "\"");
		logger.debug("load logic component description = \"" + description
				+ "\"");

		TitanLogicComponentInfo titanLogicComponentInfo = new TitanLogicComponentInfo(
				bundle.getSymbolicName(), key, name, description);

		return titanLogicComponentInfo;
	}

	private static INodeInfo getStartNodeInfo(Bundle bundle, Element root)
			throws TitanComponentException {

		Element start = root.element(KEY_NODETYPE_START);

		String name = start.elementText(KEY_NODE_NAME);
		String description = start.elementText(KEY_NODE_DESCRIPTION);
		String nextId = start.attributeValue(KEY_NODE_NEXT);
		List<IParameterInfo> parameters = getParameterInfos(bundle, start
				.element(KEY_PARAMETER_LIST));

		logger.debug("load start node name = \"" + name + "\"");
		logger.debug("load start node description = \"" + description + "\"");
		logger.debug("load start node nextId = \"" + nextId + "\"");

		return new StartNodeInfo(name, description, nextId, parameters);
	}

	@SuppressWarnings("unchecked")
	private static List<INodeInfo> getBaseComponentNodeInfo(Bundle bundle,
			Element root) throws TitanComponentException {

		List<Element> baseComponentElements = root
				.elements(KEY_NODETYPE_COMPONENT);
		List<INodeInfo> baseComponentNodeInfos = new ArrayList<INodeInfo>(
				baseComponentElements.size());

		for (Element baseComponentElement : baseComponentElements) {
			List<IParameterInfo> parameters = getParameterInfos(bundle,
					baseComponentElement.element(KEY_PARAMETER_LIST));

			String id = baseComponentElement.attributeValue(KEY_NODE_ID);
			String name = baseComponentElement.elementText(KEY_NODE_NAME);
			String description = baseComponentElement
					.elementText(KEY_NODE_DESCRIPTION);
			String nextId = baseComponentElement.attributeValue(KEY_NODE_NEXT);
			String key = baseComponentElement.elementText(KEY_NODE_KEY);

			logger.debug("load component node id = \"" + id + "\"");
			logger.debug("load component node key = \"" + key + "\"");
			logger.debug("load component node name = \"" + name + "\"");
			logger.debug("load component node description = \"" + description
					+ "\"");
			logger.debug("load component node nextId = \"" + nextId + "\"");

			BaseComponentNodeInfo baseComponentNodeInfo = new BaseComponentNodeInfo(
					id, name, description, nextId, parameters, key);

			baseComponentNodeInfos.add(baseComponentNodeInfo);
		}

		return baseComponentNodeInfos;
	}

	@SuppressWarnings("unchecked")
	private static List<INodeInfo> getLogicComponentNodeInfo(Bundle bundle,
			Element root) throws TitanComponentException {
		List<Element> logicComponentElements = root
				.elements(KEY_NODETYPE_LOGIC);
		List<INodeInfo> logicComponentNodeInfos = new ArrayList<INodeInfo>(
				logicComponentElements.size());

		for (Element logicComponentElement : logicComponentElements) {
			String id = logicComponentElement.attributeValue(KEY_NODE_ID);
			String name = logicComponentElement.elementText(KEY_NODE_NAME);
			String description = logicComponentElement
					.elementText(KEY_NODE_DESCRIPTION);
			String nextId = logicComponentElement.attributeValue(KEY_NODE_NEXT);
			String key = logicComponentElement.elementText(KEY_NODE_KEY);

			logger.debug("load logic node id = \"" + id + "\"");
			logger.debug("load logic node key = \"" + key + "\"");
			logger.debug("load logic node name = \"" + name + "\"");
			logger.debug("load logic node description = \"" + description
					+ "\"");
			logger.debug("load logic node nextId = \"" + nextId + "\"");

			List<IParameterInfo> parameters = getParameterInfos(bundle,
					logicComponentElement.element(KEY_PARAMETER_LIST));

			LogicComponentNodeInfo endNodeInfo = new LogicComponentNodeInfo(id,
					name, description, nextId, parameters, key);

			logicComponentNodeInfos.add(endNodeInfo);
		}

		return logicComponentNodeInfos;
	}

	@SuppressWarnings("unchecked")
	private static List<INodeInfo> getEndNodeInfo(Bundle bundle, Element root)
			throws TitanComponentException {

		List<Element> endElements = root.elements(KEY_NODETYPE_END);
		List<INodeInfo> endNodeInfos = new ArrayList<INodeInfo>(endElements
				.size());

		for (Element endElement : endElements) {
			String id = endElement.attributeValue(KEY_NODE_ID);
			String name = endElement.elementText(KEY_NODE_NAME);
			String description = endElement.elementText(KEY_NODE_DESCRIPTION);

			logger.debug("load end node id = \"" + id + "\"");
			logger.debug("load end node name = \"" + name + "\"");
			logger.debug("load end node description = \"" + description + "\"");

			EndNodeInfo logicComponentNodeInfo = new EndNodeInfo(id, name,
					description);

			endNodeInfos.add(logicComponentNodeInfo);
		}

		return endNodeInfos;
	}

	private static IBaseComponentInfo createBaseComponentInfo(Bundle bundle,
			Element component) throws TitanComponentException {

		String componetClass = component.attributeValue(KEY_COMPONENT_CLASS);

		String key = component.attributeValue(KEY_MODULE_KEY);
		String name = component.attributeValue(KEY_MODULE_NAME);
		String description = component.elementText(KEY_MODULE_DESCRIPTION);
		Class<?> componentType = loadClass(bundle, componetClass);

		logger.debug("load base component symbolicName = \""
				+ bundle.getSymbolicName() + "\"");
		logger.debug("load base component key = \"" + key + "\"");
		logger.debug("load base component name = \"" + name + "\"");
		logger.debug("load base component description = \"" + description
				+ "\"");
		logger.debug("load base component componentType = \"" + componentType
				+ "\"");

		return new TitanBaseComponentInfo(bundle.getSymbolicName(), key, name,
				description, componentType, getParameterInfos(bundle, component
						.element(KEY_PARAMETER_LIST)));
	}

	@SuppressWarnings("unchecked")
	private static List<IParameterInfo> getParameterInfos(Bundle bundle,
			Element parameters) throws TitanComponentException {

		if (parameters != null) {
			logger.debug("load parameters:");

			parameters.elements(KEY_PARAMETER);

			List<Element> parameterElements = parameters
					.elements(KEY_PARAMETER);
			List<IParameterInfo> parameterInfos = new ArrayList<IParameterInfo>();

			for (Element parameterElement : parameterElements) {
				parameterInfos
						.add(createParameterInfo(bundle, parameterElement));
			}

			Collections.sort(parameterInfos);

			return parameterInfos;
		}

		return null;
	}

	private static IParameterInfo createParameterInfo(Bundle bundle,
			Element parameter) throws TitanComponentException {
		String parameterClass = parameter
				.attributeValue(KEY_PARAMETER_CLASS_TYPE);

		Class<?> parameterType = loadClass(bundle, parameterClass);

		int scope = IParameterInfo.SCOPE_LOGIC;

		try {
			scope = Integer
					.parseInt(parameter.elementText(KEY_PARAMETER_SCOPE));
		} catch (NumberFormatException e) {
			e.printStackTrace();
		}

		String id = parameter.attributeValue(KEY_PARAMETER_ID);
		String name = parameter.attributeValue(KEY_PARAMETER_NAME);
		String passingType = parameter
				.attributeValue(KEY_PARAMETER_PASSING_TYPE);
		String valueKey = parameter.elementText(KEY_PARAMETER_VALUE_KEY);
		String description = parameter.elementText(KEY_PARAMETER_DECRRIPTION);

		logger.debug("     load parameter id = \"" + id + ", name = \"" + name
				+ ", parameterType = \"" + parameterClass
				+ ", passingType = \"" + passingType + ", valueKey = \""
				+ valueKey + ", description = \"" + description
				+ ", scope = \"" + scope);

		return new TitanParameterInfo(id, name, parameterType, passingType,
				valueKey, description, scope);
	}

	private static Class<?> loadClass(Bundle bundle, String className)
			throws TitanComponentException {
		Class<?> parameterType = null;
		try {
			parameterType = bundle.loadClass(className);
		} catch (ClassNotFoundException e) {
			throw new TitanComponentException("class not found [" + className
					+ "]");
		}

		return parameterType;
	}
}
